// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/deribit_base/api_order/fix/fix_message_builder.h"

#include <utility>

#include <glog/logging.h>

#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/deribit_base/api_order/fix/fix_fields.h"
#include "coin2/exchange/deribit_futures/symbology/product.h"
#include "coin2/exchange/deribit_options/symbology/product.h"
#include "quickfix/fix44/NewOrderSingle.h"
#include "quickfix/fix44/OrderCancelRequest.h"
#include "quickfix/fix44/OrderStatusRequest.h"
#include "quickfix/fix44/SecurityListRequest.h"

namespace coin2::exchange::deribit::api_order::fix {

DeribitOrder::DeribitOrder(
    const OrderExecution& ord_exec,
    const MarketExchangeApi& mea,
    GetProductHolderFunc get_product_holder,
    double contract_multiplier) {
  Parse(ord_exec, mea, get_product_holder, contract_multiplier);
  Validate();
}

void DeribitOrder::Parse(
    const OrderExecution& ord_exec,
    const MarketExchangeApi& mea,
    GetProductHolderFunc get_product_holder,
    double contract_multiplier) {
  using OrderType = coin2::exchange::base::order::OrderType;
  client_id = std::to_string(ord_exec.proc_order_id);
  symbol = ord_exec.order_spec->product;

  if (mea.market == coin::proto::Futures) {
    using coin2::exchange::deribit_futures::symbology::DeribitFuturesProduct;
    auto product = DeribitFuturesProduct::FromStr(
        ord_exec.order_spec->product,
        ord_exec.order_spec->order_created_time);
    auto product_holder = get_product_holder(product, ord_exec.order_spec->order_created_time);
    auto formatter = product_holder->product_info().order_formatter();
    // convert from USD value to num of contracts.
    quantity = stod(formatter.FormatQty(ord_exec.order_spec->qty / contract_multiplier));
    price = stod(formatter.FormatPrice(ord_exec.order_spec->price));
  } else if (mea.market == coin::proto::Options) {
    using coin2::exchange::deribit_options::symbology::DeribitOptionsProduct;
    auto product = DeribitOptionsProduct::FromStr(
        ord_exec.order_spec->product,
        ord_exec.order_spec->order_created_time);
    auto product_holder = get_product_holder(product, ord_exec.order_spec->order_created_time);
    auto formatter = product_holder->product_info().order_formatter();
    // convert from USD value to num of contracts.
    quantity = stod(formatter.FormatQty(ord_exec.order_spec->qty / contract_multiplier));
    price = stod(formatter.FormatPrice(ord_exec.order_spec->price));
  } else {
    CHECK(false) << "Unknown market type " << mea.String();
  }

  type = ord_exec.order_spec->order_type == OrderType::MARKET_ORDER ? MARKET : LIMIT;
  side = ord_exec.order_spec->order_side == 1 ? BUY : SELL;

  const auto& duration = ord_exec.order_spec->duration;
  if (duration == coin::proto::OrderDuration::FOK_ORDER) {
    time_in_force = FILL_OR_KILL;
  } else if (duration == coin::proto::OrderDuration::IOC_ORDER) {
    time_in_force = IMMEDIATE_OR_CANCEL;
  } else if (duration == coin::proto::OrderDuration::GTC_ORDER) {
    time_in_force = GOOD_TILL_CANCEL;
  } else {
    LOG(INFO) << "Unknown OrderDuration." << static_cast<int>(duration);
    time_in_force = GOOD_TILL_CANCEL;
  }

  if (ord_exec.order_spec->post_only) {
    exec_inst = "6A";  // REJECT POST_ONLY
  }
}

void DeribitOrder::Validate() const {
  // TODO(xguo) - validate fields
}

auto FixMessageBuilder::GeneratePlaceOrder(
    const OrderExecution& ord_exec,
    GetProductHolderFunc get_product_holder,
    double contract_multiplier) -> RequestTypePtr {
  DeribitOrder order{ord_exec, mea_, get_product_holder, contract_multiplier};
  RequestTypePtr message = std::make_unique<FIX44::NewOrderSingle>();
  message->setField(FIX::ClOrdID(order.client_id));
  // Make sure rest/ws can see client order id.
  message->setField(FIX::DeribitLabel(order.client_id));

  message->setField(FIX::Side(order.side));
  message->setField(FIX::OrderQty(order.quantity));
  message->setField(FIX::Symbol(order.symbol));
  message->setField(FIX::OrdType(order.type));
  if (!order.exec_inst.empty()) {
    message->setField(FIX::ExecInst(order.exec_inst));
  }
  if (LIMIT == order.type) {
    message->setField(FIX::Price(order.price));
    message->setField(FIX::TimeInForce(order.time_in_force));
  } else if (MARKET == order.type) {
    CHECK(false) << "market order not supported!";
  } else if (STOP_LIMIT == order.type) {
    CHECK(false) << "stop_limit order not supported!";
  } else {
    CHECK(false) << "not supported!";
  }
  return message;
}

auto FixMessageBuilder::GenerateCancelOrder(const OrderExecution& ord_exec) -> RequestTypePtr {
  RequestTypePtr message = std::make_unique<FIX44::OrderCancelRequest>();
  if (ord_exec.oc == nullptr) {
    LOG(ERROR) << "order context not found!";
    return message;
  }

  const std::string& symbol = ord_exec.oc->order_spec().product;
  const std::string client_id_str = std::to_string(ord_exec.proc_order_id);
  const std::string order_id_str = ord_exec.oc->exchange_order_id().substr(symbol.length());
  message->setField(FIX::OrigClOrdID(order_id_str));
  message->setField(FIX::ClOrdID(client_id_str));
  return message;
}

auto FixMessageBuilder::GenerateSecurityListRequest() -> RequestTypePtr {
  RequestTypePtr message = std::make_unique<FIX44::SecurityListRequest>();
  message->setField(FIX::SecurityReqID(std::to_string(GetCurrentTimestamp())));
  message->setField(FIX::SecurityListRequestType(0));
  return message;
}

}  // namespace coin2::exchange::deribit::api_order::fix
